package com.y.fund.handler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.y.fund.utils.ReflectUtil;
import com.y.fund.utils.StrUtil;

import jakarta.servlet.http.HttpServletRequest;


public class MultiRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {

  private static final String JSONBODY_ATTRIBUTE = "JSON_REQUEST_BODY";
  
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(MultiRequestBody.class);
  }

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    String jsonBody = getRequestBody(webRequest);
    
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
	fastJsonConfig.setFeatures(Feature.SupportAutoType);
    fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteClassName,SerializerFeature.WriteNullListAsEmpty, SerializerFeature.PrettyFormat, SerializerFeature.WriteNullListAsEmpty,
        SerializerFeature.WriteNullStringAsEmpty);
	
    JSONObject jsonObject = JSON.parseObject(jsonBody);
    MultiRequestBody parameterAnnotation = parameter.getParameterAnnotation(MultiRequestBody.class);
    String key = parameterAnnotation.name();
    Object value;

    if (null!=jsonObject && StrUtil.NotBlankIgnoreNullStr(key)) {
      value = jsonObject.get(key);
      if (value == null && parameterAnnotation.required()) {
        throw new IllegalArgumentException(String.format("required param %s is not present", key));
      }
    } else {
      key = parameter.getParameterName();
      if(null==jsonObject || null == key) {
    	  value = null;
      }else {
          value = jsonObject.get(key);
      }
    }
    
    Class<?> parameterType = parameter.getParameterType();
    if (value != null) {
      if (parameterType.isPrimitive()) {
        return parsePrimitive(parameterType.getName(), value);
      }
      if (ReflectUtil.BasicDataTypes.contains(parameterType)) {
    	  Object oo = parseBasicTypeWrapper(parameterType, value);
        return oo;
      }

      return JSON.parseObject(value.toString(), parameterType);
    }

   if (ReflectUtil.BasicDataTypes.contains(parameterType)) {
      if (parameterAnnotation.required()) {
        throw new IllegalArgumentException(String.format("required param %s is not present", key));
      } else {
        return null;
      }
    }

    if (!parameterAnnotation.parseAllFields()) {
      if (parameterAnnotation.required()) {
        throw new IllegalArgumentException(String.format("required param %s is not present", key));
      }
      return null;
    }
    Object result = null;
    if(null != jsonObject) {
        try {
          result = JSON.parseObject(jsonObject.toString(), parameterType);
        } catch (JSONException jsonException) {
          result = null;
        }
    }
    if (!parameterAnnotation.required()) {
    	if(!(parameterType.isPrimitive() || ReflectUtil.BasicDataTypes.contains(parameterType)) && null == result) {
    		if(parameterType.isArray()) {
        		result = Array.newInstance(parameterType.getComponentType(), 0);
    		}else {
        		result = parameterType.newInstance();
    		}
    	}
       return result;
    } else {
      boolean haveValue = false;
      Field[] declaredFields = parameterType.getDeclaredFields();
      for (Field field : declaredFields) {
        field.setAccessible(true);
        if (null!=result && field.get(result) != null) {
          haveValue = true;
          break;
        }
      }
      if (!haveValue) {
        throw new IllegalArgumentException(String.format("required param %s is not present", key));
      }
      return result;
    }
  }

  private Object parsePrimitive(String parameterTypeName, Object value) {
    final String booleanTypeName = "boolean";
    if (booleanTypeName.equals(parameterTypeName)) {
      return Boolean.valueOf(value.toString());
    }
    final String intTypeName = "int";
    if (intTypeName.equals(parameterTypeName)) {
      return Integer.valueOf(value.toString());
    }
    final String charTypeName = "char";
    if (charTypeName.equals(parameterTypeName)) {
      return value.toString().charAt(0);
    }
    final String shortTypeName = "short";
    if (shortTypeName.equals(parameterTypeName)) {
      return Short.valueOf(value.toString());
    }
    final String longTypeName = "long";
    if (longTypeName.equals(parameterTypeName)) {
      return Long.valueOf(value.toString());
    }
    final String floatTypeName = "float";
    if (floatTypeName.equals(parameterTypeName)) {
      return Float.valueOf(value.toString());
    }
    final String doubleTypeName = "double";
    if (doubleTypeName.equals(parameterTypeName)) {
      return Double.valueOf(value.toString());
    }
    final String byteTypeName = "byte";
    if (byteTypeName.equals(parameterTypeName)) {
      return Byte.valueOf(value.toString());
    }
    return null;
  }

  private Object parseBasicTypeWrapper(Class<?> parameterType, Object value) {
    if (Number.class.isAssignableFrom(parameterType)) {
      Number number = (Number) value;
      if (parameterType == Integer.class) {
        return number.intValue();
      } else if (parameterType == Short.class) {
        return number.shortValue();
      } else if (parameterType == Long.class) {
        return number.longValue();
      } else if (parameterType == Float.class) {
        return number.floatValue();
      } else if (parameterType == Double.class) {
        return number.doubleValue();
      } else if (parameterType == Byte.class) {
        return number.byteValue();
      }
    } else if (parameterType == Boolean.class) {
      return value;
    } else if (parameterType == Character.class) {
        return value.toString().charAt(0);
    } else if (String.class.equals(parameterType)) {
    	if(null == value) {
    		return null;
    	}else {
            return ""+value;
    	}
    }
    return null;
  }
  
  @SuppressWarnings("deprecation")
  private String getRequestBody(NativeWebRequest webRequest) {
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    if(null == servletRequest) {
    	return null;
    }
    try {
		servletRequest.setCharacterEncoding("UTF-8");
	} catch (UnsupportedEncodingException e1) {
		e1.printStackTrace();
	}

    String jsonBody = null;
    String httpMethod = servletRequest.getMethod();
    
    if("GET".equalsIgnoreCase(httpMethod)) {
    	Enumeration<String> names = servletRequest.getParameterNames();
    	Map<String, Object> jsonMap = new HashMap<String, Object>();
    	while(names.hasMoreElements()) {
    		String name = names.nextElement();
    		String [] value = servletRequest.getParameterValues(name);
    		if(null != value && value.length>0) {
    			if(value.length == 1) {
    				jsonMap.put(name, value[0]);
    			}else {
    				jsonMap.put(name, value);
    			}
    		}
    	}
    	if(jsonMap.size() > 0) {
        	jsonBody = JSON.toJSONString(jsonMap);
    	}
    }else {
        jsonBody = (String) webRequest.getAttribute(JSONBODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
        if (jsonBody == null) {
          try {
        	  BufferedReader br = servletRequest.getReader();
        	  String line = null;
        	  StringBuffer buffer = new StringBuffer(4096);
        	  while ((line = br.readLine()) != null) {
        	       buffer.append(line);
        	  }
            jsonBody = buffer.toString();
            webRequest.setAttribute(JSONBODY_ATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
    }
    
    return jsonBody;
  }
}
